<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>老衲的少女心的AI小助手</title>
    <link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
    <script src="https://cdn.jsdelivr.net/npm/vue@3.2.31/dist/vue.global.min.js"></script>
    <style>
        body {
            font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
        }

        /* 滚动条样式 */
        ::-webkit-scrollbar {
            width: 6px;
        }

        ::-webkit-scrollbar-track {
            background: #f1f1f1;
        }

        ::-webkit-scrollbar-thumb {
            background: #c1c1c1;
            border-radius: 3px;
        }

        ::-webkit-scrollbar-thumb:hover {
            background: #a8a8a8;
        }

        /* 输入框自适应高度 */
        textarea {
            min-height: 44px;
            max-height: 200px;
            transition: height 0.2s;
        }

        /* 加载动画 */
        @keyframes pulse {
            0%, 100% {
                opacity: 0.5;
            }
            50% {
                opacity: 1;
            }
        }

        .animate-pulse {
            animation: pulse 1.5s infinite;
        }

        .delay-100 {
            animation-delay: 0.1s;
        }

        .delay-200 {
            animation-delay: 0.2s;
        }

        /* 打字机效果 */
        .typing-cursor::after {
            content: "|";
            animation: blink 1s step-end infinite;
        }

        @keyframes blink {
            from, to {
                opacity: 1;
            }
            50% {
                opacity: 0;
            }
        }
    </style>
</head>
<body>
<div id="app" class="flex h-screen bg-gray-50">
    <!-- 会话侧边栏 -->
    <!-- 会话侧边栏 -->
    <aside class="w-64 bg-white border-r shadow-sm p-4 overflow-y-auto" :class="darkMode ? 'bg-gray-800 border-gray-700' : ''">
        <div class="flex justify-between items-center mb-4">
            <h2 class="font-bold text-lg text-gray-800" :class="darkMode ? 'text-white' : ''">会话</h2>
            <!-- 移除加号按钮 -->
        </div>
        <div v-for="(session, index) in sessions" :key="session.id" 
             @click="switchSession(index)"
             :class="['p-4 rounded-lg cursor-pointer mb-2 transition-all duration-200 shadow-sm', 
                     currentSessionIndex === index ? 'bg-blue-50 dark:bg-blue-900/50 border-l-4 border-blue-500' : 'hover:bg-gray-50 dark:hover:bg-gray-700/70']">
            <div class="flex justify-between items-center">
                <div class="font-bold text-lg truncate text-gray-800" :class="darkMode ? 'text-white' : ''">{{ session.title }}</div>
                <button @click.stop="deleteSession(index)" class="text-gray-400 hover:text-red-500 p-1">
                    <i class="fas fa-times"></i>
                </button>
            </div>
            <div class="text-xs text-gray-500 truncate mt-1 dark:text-gray-400">
                {{ session.messages[session.messages.length - 1]?.content || '新会话' }}
            </div>
        </div>
    </aside>

    <!-- 主内容区 -->
    <div class="flex-1 flex flex-col overflow-hidden">
        <!-- 顶部导航栏 -->
        <header class="bg-white shadow-sm py-3 px-4 flex items-center justify-between" :class="darkMode ? 'bg-gray-800 border-b border-gray-700' : ''">
            <div class="flex items-center">
                <div class="text-xl font-bold text-blue-600">{{ currentSession.title || '老衲的少女心的AI小助手' }}</div>
            </div>
            <div class="flex items-center space-x-3">
                <button
                        @click="startNewConversation"
                        class="ml-2 p-3 rounded-lg bg-green-500 hover:bg-green-600 text-white"
                        title="新建会话"
                        style="width: 50px"
                >
                    <i class="fas fa-plus"></i>
                </button>
                <button @click="toggleDarkMode" class="p-2 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700">
                    <i :class="darkMode ? 'fas fa-moon text-gray-300' : 'fas fa-sun text-gray-600'"></i>
                </button>
            </div>
        </header>

        <!-- 聊天内容区域 -->
        <!-- 聊天内容区域 -->
        <main class="flex-1 overflow-y-auto p-4 space-y-6" ref="chatContainer" :class="darkMode ? 'bg-gray-800 text-white' : 'bg-gray-50'">
            <div v-for="(message, index) in messages" :key="index" class="max-w-3xl mx-auto">
                <div :class="['flex', message.role === 'user' ? 'justify-end' : 'justify-start']">
                    <div :class="['flex items-start space-x-3', message.role === 'user' ? 'flex-row-reverse space-x-reverse' : '']">
                        <div :class="['w-8 h-8 rounded-full flex items-center justify-center',
                                        message.role === 'user' ? 'bg-blue-500 text-white' : 'bg-green-500 text-white',
                                        darkMode ? 'opacity-90' : '']">
                            <i :class="message.role === 'user' ? 'fas fa-user' : 'fas fa-robot'"></i>
                        </div>
                        <div :class="['p-4 rounded-2xl max-w-lg shadow-md',
                                        message.role === 'user'
                                            ? 'bg-gradient-to-br from-blue-500 to-blue-600 text-white' 
                                            : darkMode
                                                ? 'bg-gradient-to-br from-gray-700 to-gray-800 text-gray-100 border border-gray-600'
                                                : 'bg-gradient-to-br from-white to-gray-50 text-gray-800 border border-gray-100']">
                            <div v-if="message.role === 'assistant' && message.isLoading" class="flex space-x-2">
                                <div :class="['w-2 h-2 rounded-full', darkMode ? 'bg-gray-400' : 'bg-gray-300', 'animate-pulse']"></div>
                                <div :class="['w-2 h-2 rounded-full', darkMode ? 'bg-gray-400' : 'bg-gray-300', 'animate-pulse delay-100']"></div>
                                <div :class="['w-2 h-2 rounded-full', darkMode ? 'bg-gray-400' : 'bg-gray-300', 'animate-pulse delay-200']"></div>
                            </div>
                            <div v-else class="whitespace-pre-wrap">
                                    <span v-for="(char, charIndex) in message.content" :key="charIndex"
                                          :class="{'opacity-0': charIndex >= message.visibleChars, 'fade-in': charIndex < message.visibleChars}">
                                            {{ char }}
                                        </span>
                                    <span v-if="message.isStreaming" class="typing-cursor"></span>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </main>

        <!-- 输入框区域 -->
        <footer :class="['border-t p-4', darkMode ? 'bg-gray-800 border-gray-700' : 'bg-white border-gray-200']">
        <div class="max-w-3xl mx-auto relative">
            <div class="flex items-center">
                    <textarea
                            v-model="userInput"
                            @keydown.enter.exact.prevent="sendMessage"
                            @keydown.ctrl.enter.exact.prevent="sendMessage"
                            @keydown.esc.exact="stopResponse"
                            placeholder="输入您的问题..."
                            :class="['flex-1 border rounded-lg py-3 px-4 pr-12 focus:outline-none focus:ring-2 resize-none',
                                darkMode
                                    ? 'bg-gray-700 border-gray-600 text-white focus:ring-blue-400 placeholder-gray-400'
                                    : 'border-gray-300 focus:ring-blue-500 focus:border-transparent']"
                            rows="1"
                            ref="textarea"
                            @input="adjustTextareaHeight"
                    ></textarea>
                <!-- 新建会话按钮 -->

                <button
                        @click="isLoading ? stopResponse() : sendMessage()"
                        :disabled="!userInput.trim() && !isLoading"
                        :class="['ml-2 p-3 rounded-lg',
                                isLoading
                                    ? 'bg-red-500 hover:bg-red-600 text-white'
                                    : 'bg-blue-500 hover:bg-blue-600 text-white',
                                'disabled:opacity-50 disabled:cursor-not-allowed']"
                >
                    <i :class="isLoading ? 'fas fa-stop' : 'fas fa-paper-plane'"></i>
                </button>
            </div>
        </div>
    </footer>
</div>

<script>
    const {createApp, ref, nextTick, onMounted, watch} = Vue;

    createApp({
        setup() {
            // 会话管理相关状态
            const sessions = ref([]);
            const currentSessionIndex = ref(0);
            const messages = ref([]);
            const userInput = ref('');
            const isLoading = ref(false);
            const chatContainer = ref(null);
            const textarea = ref(null);
            const darkMode = ref(false);
            const chatId = ref(Date.now().toString());

            let controller = null;
            let typingInterval = null;

            // 生成会话标题
            const generateSessionTitle = (sessionMessages) => {
                const userMessage = sessionMessages.find(m => m.role === 'user');
                if (userMessage) {
                    return userMessage.content.length > 20 ? userMessage.content.substring(0, 20) + '...' : userMessage.content;
                }
                return '新会话';
            };

            // 切换会话
            const switchSession = (index) => {
                if (currentSessionIndex.value === index) return;
                // 保存当前会话状态
                sessions.value[currentSessionIndex.value] = {
                    ...sessions.value[currentSessionIndex.value],
                    messages: [...messages.value]
                };
                // 切换到新会话
                currentSessionIndex.value = index;
                chatId.value = sessions.value[index].id;
                messages.value = [...sessions.value[index].messages];
                scrollToBottom();
            };

            // 删除会话
            const deleteSession = (index) => {
                if (sessions.value.length <= 1) {
                    alert('至少保留一个会话');
                    return;
                }
                sessions.value.splice(index, 1);
                // 调整当前会话索引
                if (currentSessionIndex.value === index) {
                    currentSessionIndex.value = Math.max(0, index - 1);
                    chatId.value = sessions.value[currentSessionIndex.value].id;
                    messages.value = [...sessions.value[currentSessionIndex.value].messages];
                } else if (currentSessionIndex.value > index) {
                    currentSessionIndex.value--;
                }
                scrollToBottom();
                saveSessionsToLocalStorage();
            };

            // 新建会话
            const startNewConversation = () => {
                // 保存当前会话（如果有消息）
                if (sessions.value.length > 0 && messages.value.length > 0) {
                    sessions.value[currentSessionIndex.value] = {
                        id: chatId.value,
                        title: generateSessionTitle(messages.value),
                        messages: [...messages.value]
                    };
                }

                // 创建新会话
                const newSessionId = Date.now().toString();
                const newSession = {
                    id: newSessionId,
                    title: '新会话',
                    messages: [{
                        role: 'assistant',
                        content: '你好！我是老衲的少女心的AI小助手，请问有什么能帮到您？',
                        isLoading: false,
                        visibleChars: 0,
                        isStreaming: false
                    }]
                };

                sessions.value.push(newSession);
                currentSessionIndex.value = sessions.value.length - 1;
                chatId.value = newSessionId;
                messages.value = [...newSession.messages];

                // 确保欢迎消息完全可见
                messages.value[0].visibleChars = messages.value[0].content.length;
                scrollToBottom();
                nextTick(() => {
                    textarea.value.focus();
                });
                saveSessionsToLocalStorage();
            };

            // 保存会话到本地存储
            const saveSessionsToLocalStorage = () => {
                localStorage.setItem('chatSessions', JSON.stringify(sessions.value));
                localStorage.setItem('currentSessionIndex', currentSessionIndex.value);
            };

            // 从本地存储加载会话
            const loadSessionsFromLocalStorage = () => {
                const savedSessions = localStorage.getItem('chatSessions');
                const savedIndex = localStorage.getItem('currentSessionIndex');
                if (savedSessions && savedIndex) {
                    try {
                        sessions.value = JSON.parse(savedSessions);
                        currentSessionIndex.value = parseInt(savedIndex);
                        chatId.value = sessions.value[currentSessionIndex.value].id;
                        messages.value = [...sessions.value[currentSessionIndex.value].messages];
                        return true;
                    } catch (e) {
                        console.error('Failed to load sessions', e);
                    }
                }
                return false;
            };

            // 调整文本区域高度
            const adjustTextareaHeight = () => {
                const textareaEl = textarea.value;
                textareaEl.style.height = 'auto';
                textareaEl.style.height = `${Math.min(textareaEl.scrollHeight, 200)}px`;
            };

            // 滚动到底部
            const scrollToBottom = () => {
                nextTick(() => {
                    if (chatContainer.value) {
                        chatContainer.value.scrollTop = chatContainer.value.scrollHeight;
                    }
                });
            };

            // 切换暗黑模式
            const toggleDarkMode = () => {
                darkMode.value = !darkMode.value;
                localStorage.setItem('darkMode', darkMode.value);
            };

            // 模拟逐字打印效果
            const startTypingEffect = (messageIndex) => {
                const message = messages.value[messageIndex];
                if (!message || message.visibleChars >= message.content.length) {
                    clearInterval(typingInterval);
                    typingInterval = null;
                    messages.value[messageIndex].isStreaming = false;
                    return;
                }

                messages.value[messageIndex].visibleChars++;
                scrollToBottom();
            };

            // 发送消息
            const sendMessage = async () => {
                if (!userInput.value.trim() || isLoading.value) return;

                // 中止之前的请求
                if (controller) {
                    controller.abort();
                }
                controller = new AbortController();

                const userMessage = {
                    role: 'user',
                    content: userInput.value.trim(),
                    isLoading: false,
                    visibleChars: userInput.value.trim().length,
                    isStreaming: false
                };

                messages.value.push(userMessage);

                const assistantMessage = {
                    role: 'assistant',
                    content: '',
                    isLoading: true,
                    visibleChars: 0,
                    isStreaming: true
                };

                messages.value.push(assistantMessage);

                userInput.value = '';
                adjustTextareaHeight();
                scrollToBottom();

                isLoading.value = true;

                try {
                    const response = await fetch(`http://localhost:9000/ai/chat/stream?query=${encodeURIComponent(userMessage.content)}&chatId=${chatId.value}`, {
                        signal: controller.signal
                    });

                    if (!response.ok) {
                        throw new Error(`HTTP error! status: ${response.status}`);
                    }

                    const reader = response.body.getReader();
                    const decoder = new TextDecoder('utf-8');
                    let buffer = '';
                    let messageIndex = messages.value.length - 1;

                    // 先清除之前的打字效果
                    if (typingInterval) {
                        clearInterval(typingInterval);
                        typingInterval = null;
                    }

                    // 开始流式处理
                    while (true) {
                        const {done, value} = await reader.read();
                        if (done) break;

                        const chunk = decoder.decode(value, {stream: true});
                        buffer += chunk;

                        // 直接更新内容
                        messages.value[messageIndex].content = buffer;
                        messages.value[messageIndex].isLoading = false;

                        // 启动打字效果
                        if (!typingInterval) {
                            typingInterval = setInterval(() => {
                                startTypingEffect(messageIndex);
                            }, 20); // 调整这个值可以改变打字速度
                        }

                        scrollToBottom();
                    }

                } catch (error) {
                    if (error.name === 'AbortError') {
                        console.log('请求被用户中止');
                    } else {
                        console.error('请求出错:', error);
                        const lastMessage = messages.value[messages.value.length - 1];
                        lastMessage.content = '抱歉，请求过程中出现错误: ' + error.message;
                        lastMessage.visibleChars = lastMessage.content.length;
                    }
                } finally {
                    const lastMessage = messages.value[messages.value.length - 1];
                    lastMessage.isLoading = false;
                    lastMessage.isStreaming = false;

                    // 确保所有字符都可见
                    if (lastMessage.visibleChars < lastMessage.content.length) {
                        lastMessage.visibleChars = lastMessage.content.length;
                    }

                    isLoading.value = false;
                    controller = null;

                    if (typingInterval) {
                        clearInterval(typingInterval);
                        typingInterval = null;
                    }

                    scrollToBottom();
                    // 更新会话标题
                    if (sessions.value.length > 0) {
                        sessions.value[currentSessionIndex.value].title = generateSessionTitle(messages.value);
                        saveSessionsToLocalStorage();
                    }
                }
            };

            // 停止响应
            const stopResponse = () => {
                if (controller) {
                    controller.abort();
                    const lastMessage = messages.value[messages.value.length - 1];
                    lastMessage.isLoading = false;
                    lastMessage.isStreaming = false;

                    if (lastMessage.visibleChars < lastMessage.content.length) {
                        lastMessage.visibleChars = lastMessage.content.length;
                    }

                    isLoading.value = false;
                    controller = null;

                    if (typingInterval) {
                        clearInterval(typingInterval);
                        typingInterval = null;
                    }
                }
            };

            // 初始化
            onMounted(() => {
                // 检查暗黑模式偏好
                darkMode.value = localStorage.getItem('darkMode') === 'true' ||
                    (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches);

                // 尝试从本地存储加载会话
                const loaded = loadSessionsFromLocalStorage();
                if (!loaded) {
                    // 初始化默认会话
                    startNewConversation();
                }

                scrollToBottom();

                // 聚焦输入框
                nextTick(() => {
                    textarea.value.focus();
                });
            });

            // 监听消息变化自动滚动
            watch(messages, scrollToBottom, {deep: true});

            // 监听会话变化保存到本地存储
            watch([sessions, currentSessionIndex], saveSessionsToLocalStorage, {deep: true});

            return {
                messages,
                userInput,
                isLoading,
                darkMode,
                chatContainer,
                textarea,
                sendMessage,
                stopResponse,
                toggleDarkMode,
                adjustTextareaHeight,
                startNewConversation,
                sessions,
                currentSessionIndex,
                switchSession,
                deleteSession,
                currentSession: {
                    get() {
                        return sessions.value[currentSessionIndex.value] || {title: '老衲的少女心的AI小助手'}; 
                    }
                }
            };
        }
    }).mount('#app');
</script>
</body>
</html>
<style>
    /* 聊天气泡动画 */
    .message-bubble {
        transition: all 0.2s ease-out;
    }
    
    .message-bubble:hover {
        transform: translateY(-2px);
    }
    
    /* 气泡出现动画 */
    @keyframes fadeInUp {
        from {
            opacity: 0;
            transform: translateY(10px);
        }
        to {
            opacity: 1;
            transform: translateY(0);
        }
    }
    
    .message-enter {
        animation: fadeInUp 0.3s ease-out forwards;
    }
</style>